﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace DesertKharonIrcPlugin.Emotes.Twitch
{
    public class TwitchEmoteCacheUpdater : IDisposable
    {
        public event ProgressChangedEventHandler ProgressChanged;
        public event RunWorkerCompletedEventHandler Completed;

        private AbortableBackgroundWorker _backgroundWorker = new AbortableBackgroundWorker();

        private string _destinationPath;
        private bool _importStandard = false;
        private bool _importSubscriber = false;
        private bool _importBetterTtv = false;

        public TwitchEmoteCacheUpdater(string destinationPath, bool standard, bool subscriber, bool betterTtv)
        {
            _destinationPath = destinationPath;
            _importStandard = standard;
            _importSubscriber = subscriber;
            _importBetterTtv = betterTtv;

            _backgroundWorker.WorkerReportsProgress = true;
            _backgroundWorker.WorkerSupportsCancellation = true;
            _backgroundWorker.ProgressChanged += BackgroundWorker_ProgressChanged;
            _backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
            _backgroundWorker.DoWork += BackgroundWorker_DoWork;
        }

        public void Start()
        {
            if (_backgroundWorker.IsBusy == false)
            {
                TwitchEmoteCacheUpdaterArgument argument = new TwitchEmoteCacheUpdaterArgument
                {
                    DestinationPath = _destinationPath,
                    ImportStandard = _importStandard,
                    ImportSubscriber = _importSubscriber,
                    ImportBetterTtv = _importBetterTtv
                };

                _backgroundWorker.RunWorkerAsync(argument);
            }
        }

        private bool _cancelRequested = false;
        public void Cancel()
        {
            if (_backgroundWorker.IsBusy && _cancelRequested == false)
            {
                _backgroundWorker.CancelAsync();
                _cancelRequested = true;
            }
        }

        private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            TwitchEmoteCacheUpdaterArgument argument = e.Argument as TwitchEmoteCacheUpdaterArgument;
            if (argument != null)
            {
                int sources = CountEmoteSources(argument);
                if (sources > 0)
                {
                    TwitchEmoteCacheUpdaterState state = new TwitchEmoteCacheUpdaterState { InformationMaximum = sources };
                    _backgroundWorker.ReportProgress(0, state);

                    if (_backgroundWorker.CancellationPending)
                    {
                        e.Cancel = true;
                        return;
                    }

                    List<TwitchEmote> emoteList = GatherEmoteInformation(argument, state);

                    if (_backgroundWorker.CancellationPending)
                    {
                        e.Cancel = true;
                        return;
                    }

                    List<string> imageUrlList = emoteList
                        .GroupBy(g => g.Url)
                        .Select(x => x.Key)
                        .ToList();

                    if (_backgroundWorker.CancellationPending)
                    {
                        e.Cancel = true;
                        return;
                    }

                    if (imageUrlList.Count > 0)
                    {
                        state.DownloadMaximum = imageUrlList.Count;
                        _backgroundWorker.ReportProgress(33, state);

                        Stopwatch stopwatch = Stopwatch.StartNew();
                        float stepValue = 33f / (float)imageUrlList.Count;

                        for (int i = 0; i < imageUrlList.Count; i++)
                        {
                            if (_backgroundWorker.CancellationPending)
                            {
                                e.Cancel = true;
                                return;
                            }

                            DownloadEmote(imageUrlList[i]);
                            state.DownloadCount += 1;

                            if (stopwatch.ElapsedMilliseconds > 33)
                            {
                                _backgroundWorker.ReportProgress(33 + (int)(stepValue * ((float)i + 1f)), state);
                                stopwatch.Restart();
                            }
                        }

                        _backgroundWorker.ReportProgress(67, state);
                        stopwatch.Stop();
                    }
                }
            }
        }

        // ToDo: Add error message list to dialog and log errors there ...
        private bool DownloadEmote(string url)
        {
            if (String.IsNullOrWhiteSpace(url))
                throw new ArgumentNullException("url");

            Uri uri = null;
            try
            {
                uri = new Uri(url);
            }
            catch (UriFormatException ex)
            {
                Debug.Write(String.Format("Exception in DownloadEmote: \"{0}\" -> \"{1}\"", url, ex.Message));
                return false;
            }

            if (uri.Segments != null && uri.Segments.Length > 0)
            {
                string filename = uri.Segments.Last();
                if (String.IsNullOrWhiteSpace(filename))
                {
                    Debug.Write(String.Format("Invalid file name: \"{0}\"", url));
                    return false;
                }

                try
                {
                    using (WebClient webClient = new WebClient())
                    {
                        webClient.DownloadFile(uri, Path.Combine(_destinationPath, filename));
                        return true;
                    }
                }
                catch (Exception ex)
                {
                    Debug.Write(String.Format("Exception in DownloadEmote: \"{0}\" -> \"{1}\"", url, ex.Message));
                    return false;
                }
            }

            return false;
        }

        private List<TwitchEmote> GatherEmoteInformation(TwitchEmoteCacheUpdaterArgument argument, TwitchEmoteCacheUpdaterState state)
        {
            List<TwitchEmote> emoteList = new List<TwitchEmote>();

            int sourcesProcessed = 0;
            if (argument.ImportStandard)
            {
                emoteList.AddRange(TwitchEmoteParser.ParseStandard());
                state.InformationCount = ++sourcesProcessed;
                _backgroundWorker.ReportProgress(0, state);

                if (_backgroundWorker.CancellationPending)
                    return null;

                emoteList.AddRange(TwitchEmoteParser.ParseGlobal());
                state.InformationCount = ++sourcesProcessed;
                _backgroundWorker.ReportProgress(0, state);

                if (_backgroundWorker.CancellationPending)
                    return null;
            }

            if (argument.ImportSubscriber)
            {
                emoteList.AddRange(TwitchEmoteParser.ParseSubscriber());
                state.InformationCount = ++sourcesProcessed;
                _backgroundWorker.ReportProgress(0, state);
            }

            if (argument.ImportBetterTtv)
            {
                emoteList.AddRange(TwitchEmoteParser.ParseBetterTtv());
                state.InformationCount = ++sourcesProcessed;
                _backgroundWorker.ReportProgress(0, state);

                if (_backgroundWorker.CancellationPending)
                    return null;
            }

            return emoteList;
        }

        private static int CountEmoteSources(TwitchEmoteCacheUpdaterArgument argument)
        {
            int sources = 0;
            if (argument.ImportStandard) sources += 2;
            if (argument.ImportSubscriber) sources += 1;
            if (argument.ImportBetterTtv) sources += 1;

            return sources;
        }

        private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (ProgressChanged != null)
                ProgressChanged.Invoke(this, e);
        }

        private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            if (Completed != null)
                Completed.Invoke(this, e);
        }

        public void Dispose()
        {
            if (_backgroundWorker != null)
            {
                if (_backgroundWorker.IsBusy)
                    _backgroundWorker.CancelAsync();

                while (_backgroundWorker.IsBusy) { Application.DoEvents(); }

                _backgroundWorker.ProgressChanged -= BackgroundWorker_ProgressChanged;
                _backgroundWorker.RunWorkerCompleted -= BackgroundWorker_RunWorkerCompleted;
                _backgroundWorker.DoWork -= BackgroundWorker_DoWork;

                _backgroundWorker.Dispose();
                _backgroundWorker = null;
            }
        }
    }
}
